//
// Created by LiuYou on 2021/8/20.
//

#ifndef HIGHPERFORMANCELINUXSERVERPROGRAMMING_LINUX2_HTTPCONNECT2_HPP
#define HIGHPERFORMANCELINUXSERVERPROGRAMMING_LINUX2_HTTPCONNECT2_HPP


#include <netinet/in.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <mysql.h>
#include "HttpConnectNeed.hpp"


// 开始: epoll相关设置。

/**
 * @brief 将文件描述符设置为非阻塞模式。
 * @param fd
 * @return
 */
extern int setFdNonblocking(int fd);

/**
 * @brief 内核事件表注册事件。
 * <p>
 * 内核事件表注册新事件，开启EPOLLONESHOT，针对客户端连接的描述符，listenfd不用开启。
 * @param epollFd
 * @param fd
 * @param oneShot
 */
extern void addFdToEpoll(int epollFd, int fd, bool oneShot);

/**
 * @brief 内核事件表删除事件。
 * @param epollFd
 * @param fd
 */
extern void removeFd(int epollFd, int fd);

/**
 * @brief 重置EPOLLONESHOT事件。
 * <p>
 * modify: 修改, 改变, 调整。
 * @param epollFd
 * @param fd
 * @param events
 */
extern void modFd(int epollFd, int fd, EPOLL_EVENTS events);


// 结束



class DbConnectionPool;

class HttpConnect {
public:
    void initMysqlResult(DbConnectionPool* connPool);

public:
    // The breakpoint will not currently be hit. No executable code is associated with this line.
    // 当前不会命中断点。没有可执行代码与此行相关联。
    // todo 笔记: 我把默认函数从实现文件搬到了头文件中。就可以命中这个断点了。
    HttpConnect() {}

    ~HttpConnect() {}

private:
    /** 初始化连接。内部使用。*/
    void init();

    /** 解析HTTP请求。
     * <p>
     * 主状态机。
     * <p>
     * 从状态机负责读取报文的一行，主状态机负责对该行数据进行解析，主状态机内部调用从状态机，从状态机驱动主状态机。
     * <p>
     * 主状态机, 三种状态，标识解析位置。
     * <p>
     *   CHECK_STATE_REQUESTLINE，解析请求行
     * <p>
     *   CHECK_STATE_HEADER，解析请求头
     * <p>
     *   CHECK_STATE_CONTENT，解析消息体，仅用于解析POST请求
     * */
    HttpCode processRead();

    /**
     * @brief 从状态机。用于解析出一行内容。
     * <p>
     * 从状态机, 三种状态，标识解析一行的读取状态。
     * <p>
     *   LINE_OK，完整读取一行
     * <p>
     *   LINE_BAD，报文语法有误
     * <p>
     *   LINE_OPEN，读取的行不完整
     * @return
     */
    LineStatus parseLine();

    char* getLine();

    HttpCode parseRequestLine(char* text);

    HttpCode parseHeaders(char* text);

    HttpCode doRequest();

    HttpCode parseContent(char* text);

    /**
     * @brief 对 HTTP 请求做出应答。
     * @param code
     * @return
     */
    bool processWrite(HttpCode code);

    bool addStatusLine(int status, const char* title);

    bool addResponse(const char* format, ...);

    bool addHeaders(size_t contentLength);

    bool addContentLength(size_t contentLength);

    bool addLinger();

    bool addBlankLine();

    bool addContent(const char* content);

    void unmap();

public:
    /** 初始化连接。*/
    void init(int connectionFd, const sockaddr_in& address);

    /** 解析HTTP请求。*/
    void closeConnection(bool realClose = true);

    /** 处理客户请求。
     * <p>
     * 由线程池中的工作线程调用, 这是处理 HTTP 请求的入口函数。*/
    void process();

    /** 非阻塞读操作。
     * <p>
     * 循环读取客户数据, 直到无数据可读或者对方关闭连接。
     * <p>
     * read()方法, 读取浏览器端发送来的请求报文, 直到无数据可读或对方关闭连接,
     * 读取到m_read_buffer中, 并更新m_read_idx。*/
    bool read();

    /** 非阻塞写操作。*/
    bool write();


public:
    /** 所有socket上的事件都被注册到同一个epoll内核时间表中, 所以将epoll文件描述符设置为静态的。*/
    static int epollFd;

    /** 统计用户数量。*/
    static int userCount;

    /**
     * @brief 注册登录使用到了与数据库交互。
     */
    MYSQL* mysql;

private:
    /** 该HTTP连接的socket。
     * <p>
     * 服务器与 http客户端通信的 socket套接字捏着的文件描述符 connectFd。
     * */
    int connectFd;

    /** 对方的socket地址。*/
    sockaddr_in remoteAddress;

public:
    /** 读缓冲区的大小。*/
    static const int READ_BUFFER_SIZE = 2048;

private:
    /** 读缓冲区。*/
    char readBuf[READ_BUFFER_SIZE];

    /** 标识读缓冲中已经读入的客户数据的最后一个字节的下一个位置。*/
    int readIdx;

    /** 当前正在分析的字符在读缓冲区中的位置。
     * m_read_buf读取的位置m_checked_idx。
     */
    int checkedIdx;

    /** 当前正在解析的行的起始位置。
     * m_read_buf中已经解析的字符个数。*/
    int startLine;

public:
    /** 写缓冲区的大小。*/
    static const int WRITE_BUFFER_SIZE = 1024;

private:
    /** 写缓冲区。*/
    char writeBuf[WRITE_BUFFER_SIZE];

    /** 写缓冲区中待发送的字节数。
     * 指示buffer中的长度。*/
    int writeIdx;

private:
    /** 主状态机当前所处的状态。*/
    CheckState checkState;

    /** http请求报文的请求方法。*/
    Method method;

public:
    /** 文件名的最大长度。*/
    static const int FILE_NAME_LEN = 200;

private:
    /** 客户请求的目标文件的完整路径, 其内容等于 doc_root + m_url, doc_root是网站根目录。*/
    char realFile[FILE_NAME_LEN];

    /** 客户请求的目标文件的文件名。*/
    char* url;

    /** HTTP协议版本号, 我们仅支持 HTTP 1.1。*/
    char* version;

    /** 主机名。*/
    char* host;

    /** HTTP请求的消息体的长度。*/
    int contentLength;

    /** HTTP请求是否要求保持连接。*/
    bool linger;

    /** 客户请求的目标文件被 mmap()到内存中的起始位置。*/
    char* fileAddress;

    /** 目标文件的状态。通过它我们可以判断文件是否存在、是否为目录、是否可读, 并获取文件大小等信息。*/
    struct stat fileStat;

    /* 我们将采用 writev()来执行写操作, 所以定义下面两个成员: */

    /** io向量机制iovec。*/
    iovec ioVector[2];

    /** 表示被写内存块的数量。*/
    int ioVectorCount;

    /** 请求报文是否是POST方法。*/
    int cgi;

    /** 存储响应报文的报文头部数据(即从mysql数据库中查询到的用户名和密码)。*/
    char* m_string;

    /**　剩余发送字节数。
     * 要发送的字节数。*/
    int bytesToSend;

    /** 已发送字节数。*/
    int bytesHaveSend;

};


#endif //HIGHPERFORMANCELINUXSERVERPROGRAMMING_LINUX2_HTTPCONNECT2_HPP
